/*
* Copyright 2011 Pavel Kozhin 
*
* Licensed under the Apache License, Version 2.0 (the "License"); 
* you may not use this file except in compliance with the License. 
* You may obtain a copy of the License at 
* 
* http://www.apache.org/licenses/LICENSE-2.0
* 
* Unless required by applicable law or agreed to in writing, software 
* distributed  under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 
* See the License for the specific language governing permissions and 
* limitations under the License.
*
* Author: Pavel Kozhin.
*/
package org.etherframework.mediator
{
	import flash.display.DisplayObject;
	import flash.errors.IllegalOperationError;
	import flash.events.Event;
	import flash.utils.Dictionary;
	
	import org.etherframework.IEther;
	import org.etherframework.core.$Globals;
	import org.etherframework.core.bridge.IBridge;
	import org.etherframework.core.bridge.IBridgeFacades;
	import org.etherframework.core.bridge.IObjectController;
	import org.etherframework.core.common.IArgumentFactory;
	import org.etherframework.core.common.IControllerFactory;
	import org.etherframework.core.context.Context;
	import org.etherframework.core.context.IContexts;
	import org.etherframework.core.context.IContextual;
	import org.etherframework.core.context.ISingleContextual;
	import org.etherframework.core.module.IModulePartFacade;
	import org.etherframework.core.module.ModulePart;
	import org.etherframework.core.module.ModulePartFactory;
	import org.etherframework.core.reflection.Parameter;
	import org.etherframework.core.utils.ClassUtils;
	
	[ExcludeClass]
	public class Mediators extends ModulePart implements IMediators, ISingleContextual
	{
		private static const VIEWS:Dictionary = new Dictionary(true);
		
		private const _info:Dictionary = new Dictionary();
		private const _viewsRef:Dictionary = VIEWS;
		private var _contexts:IContexts;
		private var _context:Context;
		private var _owner:IMediators;
		
		public static function initialize():void
		{
			$Globals.config.mediatorsFactory = new ModulePartFactory(Mediators);
		}
		
		public function Mediators(owner:IMediators, context:Context)
		{
			if (!context)
				throw new ArgumentError("Argument context:Context can not be null.");
			
			_owner = owner;
			_context = context;
		}
		
		override public function init(bridge:IBridge):void
		{
			super.init(bridge);
			_contexts = bridge.contexts;
		}
		
		override public function complete():void
		{
			super.complete();
			
			// Handle all visual stuff stuff created in hierarchy below.
			bridge.view.addEventListener(Event.ADDED, addedHandler);
			
			if (_info[bridge.view["constructor"]])
			{
				createMediator(bridge.view);
			}
		}
		
		public function addMediator(view:Class, mediator:Class, viewTypeOf:Class = null):void
		{
			if (!view)
				throw new ArgumentError("Argument view:Class can not be null.");
			if (!mediator)
				throw new ArgumentError("Argument mediator:Class can not be null.");
			if (_info[view])
				throw new IllegalOperationError("View " + view + " already has mediator " + 
					_info[view].mediator + ".");
			if (!ClassUtils.implementsInterface(mediator, IMediator))
				throw new ArgumentError("Class  " + mediator + " should implement " +
					"org.etherframework.mediator.IMediator.");
			
			_info[view] = new MediatorInfo(view, mediator, viewTypeOf); 
		}
		
		public function createMediator(view:DisplayObject):IMediator
		{
			if (!view)
				throw new ArgumentError("Argument view:IEventDispatcher can not be null.");
			if (_viewsRef[view])
				throw new Error("View " + view + " already has a mediator.");
			
			const info:MediatorInfo = _info[view["constructor"]];
			
			if (!info)
			{
				if (_owner)
					return _owner.createMediator(view);
				
				throw new Error("Mediator for view " + view + " is not added.");
			}
			
			const helper:MediatorsHelper = new MediatorsHelper(view, info, _viewsRef, bridge);
			
			return bridge.process(info.mediator, 
				null, helper, helper).target as IMediator;
		}
		
		public function destroyMediator(view:DisplayObject):IMediator
		{
			if (!view)
				throw new ArgumentError("Argument view:IEventDispatcher can not be null.");
			
			const result:IMediator = hasMediator(view);
			
			if (result)
				bridge.destruct(result);
			
			return result;
		}
		
		public function hasMediator(view:DisplayObject):IMediator
		{
			if (!view)
				throw new ArgumentError("Argument view:IEventDispatcher can not be null.");
			
			const controller:IObjectController = _viewsRef[view] as IObjectController;
			
			return controller ? controller.target as IMediator : null;
		}
		
		public function hasMediatorMapping(view:Class):Boolean
		{
			if (!view)
				throw new ArgumentError("Argument view:Class can not be null.");
			
			return Boolean(_info[view]) || (_owner ? _owner.hasMediatorMapping(view) : false);
		}
		
		override public function destroy():void
		{
			_contexts.dispose(this);
		}
		
		public function contextual(context:Context):IMediators
		{
			return _contexts.contextual(context, IMediators, this) as IMediators;
		}
		
		public function create(context:Context, type:Class):IContextual
		{
			var result:Mediators = new Mediators(this, context);
			result.init(bridge);
			
			return result;
		}
		
		public function dispose():void
		{
			bridge.view.removeEventListener(Event.ADDED, addedHandler);
			
			for (var item:Object in _info) 
				delete _info[item];
			
			_contexts = null;
			_owner = null;
			bridge = null;
		}
		
		override public function getFacade(owner:IBridgeFacades):IModulePartFacade
		{
			return new MediatorsFacade(owner, this);
		}
		
		public function get context():Context { return _context; }
		
		private function addedHandler(event:Event):void
		{
			if (this._info[event.target.constructor] && !this._viewsRef[event.target])
			{
				const info:MediatorInfo = _info[event.target.constructor];
				const helper:MediatorsHelper = new MediatorsHelper(
					event.target as DisplayObject, info, _viewsRef, bridge);
				
				bridge.process(info.mediator, null, helper, helper);
			}
		}
	}
}

import flash.display.DisplayObject;
import flash.utils.Dictionary;

import org.etherframework.IEther;
import org.etherframework.core.bridge.IBridge;
import org.etherframework.core.bridge.IObjectController;
import org.etherframework.core.common.IArgumentFactory;
import org.etherframework.core.common.IControllerFactory;
import org.etherframework.core.context.Context;
import org.etherframework.core.reflection.Parameter;
import org.etherframework.mediator.IMediator;
import org.etherframework.mediator.MediatorController;

final class MediatorInfo
{
	public var mediator:Class;
	
	public var view:Class;
	
	public var viewInterface:Class;
	
	public function MediatorInfo(view:Class, mediator:Class, viewInterface:Class)
	{
		this.view = view;
		this.mediator = mediator;
		this.viewInterface = viewInterface;
	}
}

final class MediatorsHelper implements IControllerFactory, IArgumentFactory
{
	private var _view:DisplayObject;
	private var _info:MediatorInfo;
	private var _views:Dictionary;
	private var _bridge:IBridge;
	
	public function MediatorsHelper(view:DisplayObject, info:MediatorInfo, 
									views:Dictionary, bridge:IBridge)
	{
		_view = view;
		_info = info;
		_views = views;
		_bridge = bridge;
	}
	
	public function createController(target:Object, ether:IEther, bridge:IBridge):IObjectController
	{
		return new MediatorController(target as IMediator, _view, 
			ether, bridge.facade, _views);
	}
	
	public function createArgument(parameter:Parameter, context:Context):Object
	{
		return (_info.view == parameter.type || 
			_info.viewInterface == parameter.type) ? _view : null;
	}
}